package tcp

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"strconv"
	"strings"

	"gitee.com/hlwqds/go-my-cache-server/cache"
	"gitee.com/hlwqds/go-my-cache-server/cluster"
)

type Server struct {
	cache.Cache
	cluster.Node
}

type result struct {
	v   []byte
	err error
}

func New(cache cache.Cache, node cluster.Node) *Server {
	return &Server{cache, node}
}

func (s *Server) Listen() {
	l, err := net.Listen("tcp", ":12346")
	if err != nil {
		panic(err)
	}
	for {
		a, err := l.Accept()
		if err != nil {
			panic(nil)
		}
		go s.process(a)
	}
}

func reply(a net.Conn, ch chan chan *result) {
	defer a.Close()
	for {
		//when ch closed by process, reply should return
		c, open := <-ch
		if !open {
			log.Println("process closed")
			return
		}
		r := <-c
		err := sendResponse(r.v, r.err, a)
		if err != nil {
			log.Println("close connection due to error:", err)
			return
		}
	}
}

//process: command = op key | key-value; op = 'S' | 'G' | 'D'
func (s *Server) process(a net.Conn) {
	r := bufio.NewReader(a)
	resultCh := make(chan chan *result, 5000)
	defer close(resultCh)
	go reply(a, resultCh)
	for {
		op, err := r.ReadByte()
		if err != nil {
			log.Println("close connection due to error:", err)
			return
		}
		switch op {
		case 'G':
			s.get(resultCh, r)
		case 'S':
			s.set(resultCh, r)
		case 'D':
			s.del(resultCh, r)
		default:
			err = errors.New("unknown op")
			s.justerror(resultCh, err)
		}
	}
}

func (s *Server) get(ch chan chan *result, r *bufio.Reader) {
	c := make(chan *result)
	ch <- c
	k, err := s.readKey(r)
	if err != nil {
		c <- &result{nil, err}
		return
	}
	go func() {
		v, err := s.Get(k)
		c <- &result{v, err}
	}()
}

func (s *Server) set(ch chan chan *result, r *bufio.Reader) {
	c := make(chan *result)
	ch <- c
	k, v, err := s.readKeyAndValue(r)
	if err != nil {
		c <- &result{nil, err}
		return
	}
	go func() {
		err := s.Set(k, v)
		c <- &result{nil, err}
	}()
}

func (s *Server) del(ch chan chan *result, r *bufio.Reader) {
	c := make(chan *result)
	ch <- c
	k, err := s.readKey(r)
	if err != nil {
		c <- &result{nil, err}
		return
	}
	go func() {
		err := s.Del(k)
		c <- &result{nil, err}
	}()
}

func (s *Server) justerror(ch chan chan *result, err error) {
	c := make(chan *result)
	ch <- c
	c <- &result{nil, err}
}

// readKey:	key = bytes-array; bytes-array = length SP content
func (s *Server) readKey(r *bufio.Reader) (string, error) {
	kLen, err := readLen(r)
	if err != nil {
		return "", err
	}
	k := make([]byte, kLen)
	_, err = io.ReadFull(r, k)
	if err != nil {
		return "", err
	}
	key := string(k)
	addr, ok := s.ShouldProcess(key)
	if !ok {
		return "", errors.New("redirect " + addr)
	}

	return key, nil
}

// readLen: read until content start and return content length
func readLen(r *bufio.Reader) (int, error) {
	tmp, err := r.ReadString(' ')
	if err != nil {
		return 0, err
	}
	l, err := strconv.Atoi(strings.TrimSpace(tmp))
	if err != nil {
		return 0, err
	}
	return l, nil
}

//readKeyAndValue: key-value = length SP length SP content content
func (s *Server) readKeyAndValue(r *bufio.Reader) (string, []byte, error) {
	kLen, err := readLen(r)
	if err != nil {
		return "", nil, err
	}
	vLen, err := readLen(r)
	if err != nil {
		return "", nil, err
	}
	k := make([]byte, kLen)
	_, err = io.ReadFull(r, k)
	if err != nil {
		return "", nil, err
	}

	key := string(k)
	addr, ok := s.ShouldProcess(key)
	if !ok {
		return "", nil, errors.New("redirect " + addr)
	}

	v := make([]byte, vLen)
	_, err = io.ReadFull(r, v)
	if err != nil {
		return "", nil, err
	}
	return string(k), v, nil
}

//sendResponse:	response = error | bytes-array; error = '-' bytes-array
func sendResponse(value []byte, err error, conn net.Conn) error {
	if err != nil {
		errString := err.Error()
		tmp := fmt.Sprintf("-%d ", len(errString)) + errString
		_, e := conn.Write([]byte(tmp))
		return e
	}
	tmp := fmt.Sprintf("%d ", len(value))
	_, e := conn.Write(append([]byte(tmp), value...))
	return e
}
